package blue.squares;

import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.Window;
import android.os.Handler;
import android.graphics.Point;

/**
 *
 * @author Luisa Lu
 */
public class Squares extends Activity {

    // ------------------------------------------------------------------------
    // Constants.
    // ------------------------------------------------------------------------

    static final float ACCEPTANCE_RADIUS_SQ = 0.25f;
    
    static final int MENU_UNDO = Menu.FIRST;
    static final int MENU_RESTART = Menu.FIRST + 1;
    static final int MENU_SETTINGS = Menu.FIRST + 2;
    static final int MENU_INSTRUCTIONS = Menu.FIRST + 3;
    static final int MENU_ABOUT = Menu.FIRST + 4;
    
    static final int DIALOG_FIRST = 100;
    static final int DIALOG_START = DIALOG_FIRST;
    static final int DIALOG_INSTRUCTIONS = DIALOG_FIRST + 1;
    static final int DIALOG_ABOUT = DIALOG_FIRST + 2;
    static final int DIALOG_SETTINGS = DIALOG_FIRST + 3;

    // Computer moves should not be faster that this (in msec).
    static final int MIN_COMPUTER_MOVE_TIME = 1000;

    static final String BUNDLE_GAME = "squares.game";

    // ------------------------------------------------------------------------
    // Fields.
    // ------------------------------------------------------------------------

    SimpleDialog _startDialog;
    SimpleDialog _instructionDialog;
    SimpleDialog _aboutDialog;
    PreferenceDialog _preferenceDialog;

    Game _game;
    ScreenView _screenView;
    int _lastMove;

    // ------------------------------------------------------------------------
    // Implementation.
    // ------------------------------------------------------------------------

    /**
     * Effectively the starting point
     * @param savedInstanceState
     */
    @Override
    public void onCreate(Bundle inState){
        Util.logDebug(this, "onCreate: "+inState);
        super.onCreate(inState);

        // turn off the window's title bar
        requestWindowFeature(Window.FEATURE_NO_TITLE);

        // create view if necessary
        if(_screenView == null){
            _screenView = new ScreenView(this);
            setContentView(_screenView);
        }

        // create or load saved game
        boolean newInstance = _game == null;
        if(_game == null){
            if(loadGame(inState)){
                newInstance = false;
            }else{
                newGame();
            }
        }

        if(newInstance)
            showDialog(DIALOG_START);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState){
        Util.logDebug(this, "onSaveInstanceState: "+outState);
        super.onSaveInstanceState(outState);
        saveGame(outState);
    }

    @Override
    protected Dialog onCreateDialog(int id) {
        Util.logVerbose(this, "onCreateDialog: "+id);
        switch(id){
            case DIALOG_START:
                if(_startDialog == null)
                    _startDialog = new SimpleDialog(this, R.string.app_name, R.layout.start_dialog);
                return _startDialog;
                
            case DIALOG_INSTRUCTIONS:
                if(_instructionDialog == null)
                    _instructionDialog = new SimpleDialog(this, R.string.how_to_play, R.layout.instruction_dialog);
                return _instructionDialog;

            case DIALOG_ABOUT:
                if(_aboutDialog == null)
                    _aboutDialog = new SimpleDialog(this, R.string.app_name, R.layout.about_dialog);
                return _aboutDialog;

            case DIALOG_SETTINGS:
                if(_preferenceDialog == null)
                    _preferenceDialog = new PreferenceDialog(this);
                return _preferenceDialog;
                
            default:
                Util.logDebug(this, "onCreateDialog: unknown dialog id="+id);
        }

        return super.onCreateDialog(id);
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        Util.logVerbose(this, "onCreateOptionsMenu: "+menu);
        super.onCreateOptionsMenu(menu);

        // TODO: need some icons for menu buttons
        //menu.add(0, MENU_UNDO, MENU_UNDO, R.string.undo);
        menu.add(0, MENU_RESTART, MENU_RESTART, R.string.newgame);
        menu.add(0, MENU_SETTINGS, MENU_SETTINGS, R.string.settings);
        menu.add(0, MENU_ABOUT, MENU_ABOUT, R.string.about);

        return true;        
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        Util.logVerbose(this, "onOptionsItemSelected: "+item);
        switch (item.getItemId()) {
            case MENU_UNDO: // TODO
                return true;

            case MENU_RESTART:
                newGame();
                return true;
                
            case MENU_INSTRUCTIONS:
                showDialog(DIALOG_INSTRUCTIONS);
                return true;

            case MENU_SETTINGS:
                showDialog(DIALOG_SETTINGS);
                return true;

            case MENU_ABOUT:
                showDialog(DIALOG_ABOUT);
                return true;

            default:                
                Util.logDebug(this, "onOptionsItemSelected: unknown menu="+item);
        }

        return super.onOptionsItemSelected(item);
    }

    final Handler _moveHandler = new Handler();
    final Runnable _moveUpdater = new Runnable() {
        public void run() {
            // TODO(jimbruce): Handle null strategies and invalid
            // moves gracefully.
            Player p = _game.getCurrentPlayer();
            Strategy s = p.getStrategy();
            Point c = s.getMoveChoice();
            _game.makeMove(p, c.x, c.y);
            _screenView.invalidate();
        }
    };

    private void processInput(int x, int y) {
        // Process the local input, and invalidate the screen if
        // needed.
        if(_game.processInput(x, y)) {
            _screenView.invalidate();
        }
        // If a new move has occurred, and the current player is not a
        // local human, spawn a thread for thinking.
        if(_game.getCurrentMove() > _lastMove && _game.isComputerMove()) {
            Thread t = new Thread() {
                @Override
                public void run() {
                    // Calculate the computer's move.  We get the
                    // system time to measure the time taken by the
                    // computer, and sleep for a bit if the move was
                    // "too fast" after the human made a move.
                    Strategy s = _game.getCurrentPlayer().getStrategy();
                    long t0 = System.currentTimeMillis();
                    s.think();
                    long t1 = System.currentTimeMillis();
                    int tr = MIN_COMPUTER_MOVE_TIME - (int)(t1 - t0);
                    if(tr > 0) {
                        try {
                            Thread.sleep(tr);
                        } catch(InterruptedException e) {
                            // Oh well, we tried.
                        }
                    }
                    _moveHandler.post(_moveUpdater);
                }
            };
            t.start();
        }
        _lastMove = _game.getCurrentMove();
    }

    @Override
    public boolean onTouchEvent(MotionEvent e){
        Util.logVerbose(this, "onTouchEvent: "+e);
        int action = e.getAction();

        // Handle touch up event.
        if(action == MotionEvent.ACTION_UP){
            int[] loc = toBoardLocation(e.getX(), e.getY());
            if(loc != null) processInput(loc[0], loc[1]);
            return true;
        }

        return super.onTouchEvent(e);
    }
       
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {   
        Util.logVerbose(this, "onKeyDown: "+keyCode+" "+event);     
        int x = _game.getSelected().x;
        int y = _game.getSelected().y;
        boolean acceptInput = true;
        
        // Process key input
        switch(keyCode){
            // input for moving guide circle 
            case KeyEvent.KEYCODE_DPAD_UP:
                y--; break;
            case KeyEvent.KEYCODE_DPAD_DOWN:
                y++; break;
            case KeyEvent.KEYCODE_DPAD_LEFT:
                x--; break;
            case KeyEvent.KEYCODE_DPAD_RIGHT:
                x++; break;
                
            // input for playing marble
            case KeyEvent.KEYCODE_DPAD_CENTER:
            case KeyEvent.KEYCODE_ENTER:
            case KeyEvent.KEYCODE_SPACE:
                // no-op keep value
                break;
                                
            // Unknown input, reject it
            default:
                Util.logDebug(this, "onKeyDown: unknown keycode="+keyCode+" "+event);
                acceptInput = false;
        }

        // Only accept the input if we recognize it
        if(acceptInput){
            processInput(x, y);
            return true;
        }

        return super.onKeyDown(keyCode, event);         
     }
    
    private void saveGame(Bundle outState){
        Util.logInfo(this, "saveGame");
        Bundle gameState = _game.getBundle();
        outState.putBundle(BUNDLE_GAME, gameState);
        Util.logDebug(this, _game.toString());
        Util.logDebug(this, outState.toString());
    }
    
    private boolean loadGame(Bundle inState){
        if(inState == null) return false;
        
        Bundle gameState = inState.getBundle(BUNDLE_GAME);
        if(gameState == null) return false;
        
        Util.logInfo(this, "loadGame");
        _game = new Game(gameState);
        _screenView.setGame(_game);
        _screenView.invalidate();
        Util.logDebug(this, inState.toString());
        Util.logDebug(this, _game.toString());
        return true;
    }

    public void newGame(){
        Util.logInfo(this, "newGame");
        int boardSize = PreferenceDialog.getBoardSize(this);
        int player2Type = PreferenceDialog.getPlayer2Type(this);
        _game = new Game(boardSize , player2Type);
        _screenView.setGame(_game);
        _screenView.invalidate();
        Util.logDebug(this, _game.toString());
    }
    
    private int[] toBoardLocation(float screenX, float screenY){
        float[] loc = _screenView.screenToGrid(screenX, screenY);
        float x = loc[0];
        float y = loc[1];
        int c = Math.round(x);
        int r = Math.round(y);
        float distSq = (x-c)*(x-c) + (y-r)*(y-r);
        Util.logVerbose(this, "toBoardLocation: "+x+", "+y+" ("+distSq+")");
        if(ACCEPTANCE_RADIUS_SQ > distSq){
            return new int[]{c, r};
        }
        return null;
    }
    
    // ------------------------------------------------------------------------
    // Static implementation.
    // ------------------------------------------------------------------------
    
}
